此篇博文根据斯坦福公开课 《编程范式》整理,也算是一个简单的笔记。
Stack(栈)
栈是一种十分基础的数据结构,也十分简单,下面我们首先来实现此种数据结构的int表示
stack.h
在此文件之中,我们对栈的结构和栈的基本操作进行了定义,其中代码如下:
1 2 3 4 5 6 7 8 9 10
| typedef struct { int* elems; int logLength; int allocLength; }stack;
void StackNew(stack* s); void StackDispose(stack* s); void StackPush(stack* s, int value); int StackPop(stack* s);
|
stack.c
此文件中,将 .h
文件中的函数进行了实现,代码如下:
1 2 3 4 5 6
| void StackNew(stack* s) { s->allocLength = 4; s->logLength = 0; s->elems = malloc(sizeof(int)*s->allocLength); assert(s->elems != NULL); }
|
1 2 3
| void StackDispose(stack* s) { free(s->elems); }
|
1 2 3 4 5 6 7 8
| void StackPush(stack* s, int value) { if (s->logLength == s->allocLength) { s->allocLength *= 2; s->elems = realloc(s->elems, s->allocLength*sizeof(int)); assert(s->elems != NULL); } s->elems[s->logLength++] = value; }
|
1 2 3 4
| int StackPop(stack* s) { assert(s->logLength > 0); return s->elems[s->logLength--]; }
|
说明:
assert()
:存于 assert.h
头文件之中,其传参为一个布尔值,传入值为true
之时,什么也不做;如果为flase
,就停止继续执行程序并打印错误。用于此处,是为了检测开辟空间是否成功。
StackPush()
函数可以拓展栈的大小
如同之前所讲,有了基本的 int
版,那么就可以向着泛型版转化了。
泛型化的stack
为了向泛型进化,我们必须在原结构体中加入数据类型的字节数,所以结构体结果如下:
1 2 3 4 5 6 7 8 9 10 11
| typedef struct { int* elems; int elemSize; int logLength; int allocLength; }stack;
void StackNew(stack* s, int elemSize); void StackDispose(stack* s); void StackPush(stack* s, void* elemAddr); void StackPop(stack* s, void* elemAddr);
|
同理,各个函数的写法如下:
1 2 3 4 5 6 7 8
| void StackNew(stack* s, int elemSize) { assert(s->elems > 0); s->elemSize = elemSize; s->allocLength = 4; s->logLength = 0; s->elems = malloc(elemSize*s->allocLength); assert(s->elems != NULL); }
|
1 2 3
| void StackDispose(stack* s) { free(s->elems); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| static void StackGrow(stack *s) { s->allocLength *= 2; s->elems = realloc(s->elems, s->elemSize * s->allocLength); assert(s->elems != NULL); }
void StackPush(stack* s, void* elemAddr) { if (s->logLength == s->allocLength) StackGrow(s); void* target = (char*)s->elems + s->logLength*s->elemSize; memcpy(target, elemAddr, s->elemSize); s->logLength++; }
|
1 2 3 4 5
| void StackPop(stack* s, void* elemAddr) { int* source = (char*)s->elems + (s->logLength - 1) * s->elemSize; memcpy(elemAddr, source, s->elemSize); s->logLength--; }
|
说明:
- 值与值之间的传递都依靠参数传递
static
修饰的函数,只在当前文件可以调用(也就是相当于一个内部函数)
关于字符串
众所周知,字符串的传递是与众不同的,所以与其相关的一些问题也需要进行特殊处理,我们先来看一段使用字符串进行栈操作的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int main() { const char* names[] = {"Bob", "Lily", "Mike", "James"}; stack* s; char* name; StackNew(s, sizeof(char*)); for(int i = 0; i < 4; i++) { char* copy = strdup(names[i]); StackPush(s, ©); } for(int i = 0; i < 4; i++) { StackPop(s, &name); printf("%s\n", name); free(name); } StackDispose(s); return 0; }
|
由于字符串的长度是不一定的,为了简化代码,因此操作字符串之时在栈中保存的是指向该字符串的指针,但是如此一来便会产生一个问题:
当栈中还有元素之时,用户直接进行StackDispose()
操作之后,并没有释放掉指针所指向的空间,因此我们需要继续改进代码。
而怎样改进呢?首先我们应该让程序知道这个栈到底是什么类型的栈,如果是指针类型的,就要在结构体中传入一个额外的指针,指向栈所指向的地址,以便在之后更好地进行free()
,所以将结构体改进如下:
1 2 3 4 5 6 7
| typedef struct { int* elems; int elemSize; int logLength; int allocLength; void (*freefn) (void*); }stack;
|
而我们在进行 StackNew()
操作之时,就需要对栈的类型进行声明,所以将其传参改进如下:
1 2 3 4 5 6 7 8 9
| void StackNew(stack* s, int elemSize; void* freefn(void*)) { assert(s->elems > 0); s->freefn = freefn; s->elemSize = elemSize; s->allocLength = 4; s->logLength = 0; s->elems = malloc(elemSize*s->allocLength); assert(s->elems != NULL); }
|
同时,我们将对 StackDispose()
进行重写:
1 2 3 4 5 6 7 8
| void StackDispose(stack* s) { if (s->freefn != NULL) { for (int i = 0; i < s->logLength; i++) { s->freefn ((char*)s->elems + i * s->elemSize); } } free(s->elems); }
|
一般来说,字符串的释放函数应当如下:
1 2 3
| void StrFree(void *s) { free(*(char**)s); }
|